/** \file ezlcd3xx.c
 * Source file for the ezlcd3xx Arduino library for ezLCD-3xx devices by
 * EarthLCD.com. The library communicates with the ezLCD via a hardware and/or
 * serial port 
 */

/*
 * Written and maintained by Sergey Butylkov. 
 * Created on behalf of EarthLCD.com a dba of Earth Computer Technologies Inc.
 *
 * Project home: http://arduino-ezlcd3xx.googlecode.com
 *
 * Copyright (C) 2012 Sergey Butylkov
 * Copyright (C) 2012 EarthLCD.com a dba of Earth Computer Technologies, Inc.
 * 
 * This work is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version. This work is distributed in the hope that it will be 
 * useful, but without any warranty; without even the implied warranty of 
 * merchantability or fitness for a particular purpose. See the GNU Lesser 
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */
 
#include <SoftwareSerial.h>
#include <Arduino.h>
#include "ezlcd3xx.h"

#if EZLCD_SERIALDEBUG
    #define sdebug( str ) if( !isHWSerial() ) Serial.print(str)
    #define sdebugln( str ) if( !isHWSerial() ) Serial.println(str)
    bool EzLCD3xx_SW::s_sdebugInit = false;
#else
    #define sdebug( str )
    #define sdebugln( str )
#endif

EzLCD3xx::EzLCD3xx()
: m_echo( false )
, m_verbose( false )
{
}

EzLCD3xx::~EzLCD3xx()
{
}

bool EzLCD3xx::isHWSerial()
{
    // Compare the serial device used with the global instance
    // of the Arduino HardwareSerial class
    return m_pStream == &Serial;
}

void EzLCD3xx::findEzLCD()
{
    // First eat some possible buffered data from ezLCD output
    sdebugln("debug: Eating possible buffered data from ezLCD...");
    while( m_pStream->available() )
        m_pStream->read();

    while( 1 )
    {
        // Send ping...
        sendCommand("ping");
        
        // Wait 5 seconds or until <CR> for a response
        int ret = pollLCD( m_buf, EZLCD_BUFSIZE, 5000L, '\r' );
        if( ret > 0 ) // Something was received..
        {
            // Test for an echoed output
            if( !strncmp( m_buf, "ping", 4 ) )
            {
                m_echo = true;
                sdebugln( "debug: Ping command was echoed." );
                ret = pollLCD(); // Now read actual response
                if( ret == 0 ) // Verbose adds an extra <CR>. Skip it
                    ret = pollLCD();
            }
            else
                m_echo = false;

            if( ret > 0 ) // Something was received...
            {
                // Test for non-verbose output
                if( m_buf[0] == '0' )
                {
                    m_verbose = false;
                    break;
                }
                // Test for verbose
                else if( !strncmp( m_buf, "Pong", 4 ) )
                {
                    sdebugln( "debug: Verbose reply received." );
                    m_verbose = true;
                    break; // ezLCD is ready
                }
            }
        }
    }
    // Eat other possible data not processed by the ping
    // handling above.
    sdebugln("debug: Eating possible buffered data from ezLCD...");
    while( m_pStream->available() )
        m_pStream->read();
}

void EzLCD3xx::sendCommand( const char *cmd )
{
    sdebug( "Arduino says: " );
    sdebugln( cmd );

    m_pStream->print( cmd );
    m_pStream->write( '\r' ); // Append carriage-return
}

void EzLCD3xx::sendCommand()
{
    sdebug("Arduino says: ");
    sdebugln( m_buf );

    m_pStream->print( m_buf );
    m_pStream->write( '\r' ); // Append carriage-return
}

int EzLCD3xx::pollLCD( char *buf, size_t bufsize, unsigned long msTimeout, char terminator )
{   
    m_pStream->setTimeout( msTimeout );
    int num = m_pStream->readBytesUntil( terminator, buf, bufsize );
    if( num > 0 ) // Something was received
    {
        if( num >= bufsize ) // Make sure there is enough room
            num = bufsize-1; // for null termination
        buf[num] = 0;        // Null-terminate the buffer
        sdebug( "ezLCD response: ");
        sdebugln( buf );
    }
    else
    {
        sdebugln( "ezLCD response: <CR>/timeout" );
        //sdebugln( buf );
    }
    return num;
}

int EzLCD3xx::pollLCD()
{
    return pollLCD( m_buf, EZLCD_BUFSIZE, 100L, '\r' );
}

int EzLCD3xx::pollLCD( unsigned long timeout )
{
    return pollLCD( m_buf, EZLCD_BUFSIZE, timeout, '\r' );
}

int EzLCD3xx::simpleCmd()
{
    sendCommand();  // Send the contents of the buffer to ezLCD
    if( m_echo )
        pollLCD();         // Read echo response, if needed
    if( m_verbose )
        pollLCD();         // Read additional <CR> that gets generated in verbose
    int ret = pollLCD();   // Read actual response
    if( ret <= 0 )                       // Solitary carriage-returns
        while( m_pStream->available() )  // can get us here
            m_pStream->read();
    return ret;
}

int EzLCD3xx::readInt( char* &ptr )
{
    // Move pointer forward while it's not a digit or minus
    while( *ptr && ( *ptr < '0' || *ptr > '9' )  && *ptr != '-' )
        ++ptr;
    const char *set = ptr; // Mark this pointer as beginning
    // Move pointer forward as long as it's a digit or minus
    while( *ptr && ( *ptr >= '0' && *ptr <= '9' || *ptr == '-' ) )
        ++ptr;
    *ptr = 0; // Mark this pointer as the end of the number string
    ++ptr;    // Increment pointer for subsequent use
    return atoi(set); // Convert the number string
}

int EzLCD3xx::readInt()
{
    char *ptr=m_buf;
    return readInt(ptr);
}

const char* EzLCD3xx::jstStr( uint32_t align )
{
    switch( align )
    {
        case LEFTTOP:     return "LT"; break;
        case TOP:         return "CT"; break;
        case RIGHTTOP:    return "RT"; break;
        case LEFT:        return "LC"; break;
        case CENTER:      return "CC"; break;
        case RIGHT:       return "RC"; break;
        case LEFTBOTTOM:  return "LB"; break;
        case BOTTOM:      return "CB"; break;
        case RIGHTBOTTOM: return "RB"; break;
    }
    return "";
}

void EzLCD3xx::reset()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "reset" );
#else
    sprintf( m_buf, "%d", zReset );
#endif
    simpleCmd();
    findEzLCD(); // Attempt to establish communication after reset
}

void EzLCD3xx::cls( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "cls %d", id );
#else
    sprintf( m_buf, "%d %d", Clr_Screen, id );
#endif
    simpleCmd();
}

void EzLCD3xx::cls( const char *color )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "cls %s", color );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %s", Clr_Screen, color );
#endif
    simpleCmd();
}

bool EzLCD3xx::ping()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "ping" );
#else
    sprintf( m_buf, "%d", Ping );
#endif
    int ret = simpleCmd();
    if( ret <= 0 )
    {
        sdebugln( "debug: ping failed" );
        return false;
    }
    if( m_buf[0] == '0' || !strncmp( m_buf, "Pong", 4 ) )
    {
        sdebugln( "debug: ping successful" );
        return true;
    }
    sdebugln( "debug: ping failed" );
    return false;
}

void EzLCD3xx::verbose( bool val )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "verbose %d", val ? 1 : 0 );
#else
    sprintf( m_buf, "%d %c", Verbose, val ? '1' : '0' );
#endif
    m_verbose = val;
    simpleCmd();
}

void EzLCD3xx::echo( bool val )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "echo %d", val ? 1 : 0 );
#else
    sprintf( m_buf, "%d %s", Lecho, val ? "on" : "off" );
#endif
    m_echo = val;
    simpleCmd();
}

void EzLCD3xx::light( int brightness )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "light %d", brightness );
#else
    sprintf( m_buf, "%d %d", Light, brightness );
#endif
    simpleCmd();
}

int EzLCD3xx::light()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "light" );
#else
    sprintf( m_buf, "%d", Light );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        ret = readInt();
        sdebug( "debug: light = ");
        sdebugln( ret );
        return ret;
    }
    return -1;
}

void EzLCD3xx::color( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "color %d", id );
#else
    sprintf( m_buf, "%d %d", Color, id );
#endif
    simpleCmd();
}

void EzLCD3xx::color( uint8_t *red, uint8_t *green, uint8_t *blue )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "color" );
#else
    sprintf( m_buf, "%d", Color );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        char *ptr = m_buf;
        *red = readInt(ptr);
        *green = readInt(ptr);
        *blue = readInt(ptr);

        sdebug( "debug: R=" );
        sdebug( (int)*red );
        sdebug( " G=" );
        sdebug( (int)*green );
        sdebug( " B=" );
        sdebugln( (int)*blue );
    }
    else
        sdebugln( "Error reading RGB values" );
}

void EzLCD3xx::colorid( int id, uint8_t red, uint8_t green, uint8_t blue )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "colorid %d %u %u %u",
             id, red, green, blue );
#else
    sprintf( m_buf,  "%d %d %u %u %u",
             eColor_ID, id, red, green, blue );
#endif
    simpleCmd();
}

void EzLCD3xx::colorid( int id, uint8_t *red, uint8_t *green, uint8_t *blue )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "colorid %d", id );
#else
    sprintf( m_buf, "%d %d", eColor_ID, id );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        char *ptr = m_buf;
        if( m_verbose ) // skip reading colorid which isn't used
            readInt(ptr);
        *red = readInt(ptr);
        *green = readInt(ptr);
        *blue = readInt(ptr);

        sdebug( "debug: id=" );
        sdebug( id );
        sdebug( " R=" );
        sdebug( *red );
        sdebug( " G=" );
        sdebug( *green );
        sdebug( " B=" );
        sdebugln( *blue );
    }
    else
        sdebugln( "Error reading RGB values" );
}

void EzLCD3xx::font( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "font %d", id );
#else
    sprintf( m_buf, "%d %d", Font, id );
#endif
    simpleCmd();
}

void EzLCD3xx::font( const char* fontname )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "font \"%s\"",
              fontname );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d \"%s\"", 
              Font, fontname );
#endif
    simpleCmd();
}

void EzLCD3xx::fontw( int theme, const char* fontname )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "fontw %d \"%s\"",
              theme, fontname );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %d \"%s\"",
              Fontw, theme, fontname );
#endif
    simpleCmd();
} 

void EzLCD3xx::fonto( int orientation )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "fonto %d", orientation );
#else
    sprintf( m_buf, "%d %d", Font_Orient, orientation );
#endif
    simpleCmd();
}

void EzLCD3xx::calibrate()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "calibrate" );
#else
    sprintf( m_buf, "%d", Calibrate );
#endif
    simpleCmd();
}

void EzLCD3xx::linewidth( int width )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "linewidth %d", width );
#else
    sprintf( m_buf, "%d %d", Line_Width );
#endif
    simpleCmd();
}

int EzLCD3xx::linewidth()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "linewidth" );
#else
    sprintf( m_buf, "%d", Line_Width );
#endif
    simpleCmd();
    int ret = readInt();
    sdebug( "debug: linewidth = " );
    sdebug( ret );
    return ret;
}

void EzLCD3xx::linetype( int type )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "linetype %d", type );
#else
    sprintf( m_buf, "%d %d", Line_Type, type );
#endif
    simpleCmd();
}

uint16_t EzLCD3xx::xmax()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xmax" );
#else
    sprintf( m_buf, "%d", Xmax );
#endif
    simpleCmd();
    uint16_t ret = readInt();
    sdebug( "debug: xmax = " );
    sdebugln( ret );
    return ret;
}

uint16_t EzLCD3xx::width()
{
    return xmax()+1;
}

uint16_t EzLCD3xx::ymax()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "ymax" );
#else
    sprintf( m_buf, "%d", Ymax );
#endif
    simpleCmd();
    uint16_t ret = readInt();
    sdebug( "debug: ymax = " );
    sdebugln( ret );
    return ret;
}

uint16_t EzLCD3xx::height()
{
    return ymax()+1;
}

void EzLCD3xx::xy( uint16_t x, uint16_t y )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xy %u %u", x, y );
#else
    sprintf( m_buf, "%d %u %u", eXY, x, y  );
#endif
    simpleCmd();
}

void EzLCD3xx::xy( uint32_t align )
{
    const char *jst = jstStr(align);
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xy %s", jst );
#else
    sprintf( m_buf, "%d %s", eXY, jst );
#endif
    simpleCmd();
}

void EzLCD3xx::xy( uint16_t *x, uint16_t *y )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xy" );
#else
    sprintf( m_buf, "%d", eXY );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        char* ptr = m_buf;
        *x = readInt(ptr);
        *y = readInt(ptr);
        sdebug( "debug: x = " );
        sdebug( *x );
        sdebug( " y = " );
        sdebugln( *y );
    }
}

void EzLCD3xx::xy_store( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xyid %d 1", id );
#else
    sprintf( m_buf, "%d %d 1", Location, id );
#endif
    simpleCmd();
}

void EzLCD3xx::xy_restore( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "xyid %d", id );
#else
    sprintf( m_buf, "%d %d", Location, id );
#endif
    simpleCmd();
}

void EzLCD3xx::string( int id, const char *str )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "string %d \"%s\"", id, str );
#else
    sprintf( m_buf, "%d %d %s", StringID, id, str );
#endif
    simpleCmd();
}

const char* EzLCD3xx::string( int id )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "string %d", id );
#else
    sprintf( m_buf, "%d %d", StringID, id );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        const char *ptr = m_buf;
        if( m_verbose )
        {
            while( *ptr && *ptr != ':' )
                ++ptr;
            ptr+=2; // colon and space
        }
        sdebug( "debug: string id " );
        sdebug( id );
        sdebug( " = \"" );
        sdebug( ptr );
        sdebugln( '\"' );
        return ptr;
    }
    return NULL;
}

void EzLCD3xx::plot()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "plot" );
#else
    sprintf( m_buf, "%d", Location );
#endif
    simpleCmd();
}

void EzLCD3xx::plot( uint16_t x, uint16_t y )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "plot %u %u", x, y );
#else
    sprintf( m_buf, "%d %u %u", Location, x, y );
#endif
    simpleCmd();
}

void EzLCD3xx::line( uint16_t x, uint16_t y )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "line %u %u", x, y );
#else
    sprintf( m_buf, "%d %u %u", eLine, x, y );
#endif
    simpleCmd();
}

void EzLCD3xx::box( uint16_t width, uint16_t height, bool fill )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "box %u %u %u", width, height, fill ? 1 : 0 );
#else
    sprintf( m_buf, "%d %u %u %u", Box, width, height, fill ? 1 : 0 );
#endif
    simpleCmd();
}

void EzLCD3xx::circle( uint16_t radius, bool fill )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "circle %u %u", radius, fill );
#else
    sprintf( m_buf, "%d %u %u", zCircle, radius, fill );
#endif
    simpleCmd();
}

void EzLCD3xx::arc( uint16_t radius, int16_t start, int16_t end )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "arc %u %d %d", radius, start, end );
#else
    sprintf( m_buf, "%d %u %d %d", Arc, radius, start, end );
#endif
    simpleCmd();
}

void EzLCD3xx::pie( uint16_t radius, int16_t start, int16_t end )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "pie %u %d %d", radius, start, end );
#else
    sprintf( m_buf, "%d %u %d %d", Pie, radius, start, end );
#endif
    simpleCmd();
}

void EzLCD3xx::picture( int id, uint16_t x, uint16_t y, 
                        uint32_t options )
{
    int option_send;
    switch( options )
    {
        case CENTER:   option_send = 1; break;
        case DOWNSIZE: option_send = 2; break;
        case BOTH:     option_send = 3; break;
        default:       option_send = 0; break; //NONE
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "picture %u %u %d %d", x, y, option_send, id );
#else
    sprintf( m_buf, "%d %u %u %d %d", Picture, x, y, option_send, id );
#endif
    simpleCmd();
}

void EzLCD3xx::image( int id, uint16_t x, uint16_t y,
                      uint32_t options )
{
    picture( id, x, y, options );
}

void EzLCD3xx::picture( const char *filename, uint16_t x, uint16_t y,
                        uint32_t options )
{
    int option_send;
    switch( options )
    {
        case CENTER:   option_send = 1; break;
        case DOWNSIZE: option_send = 2; break;
        case BOTH:     option_send = 3; break;
        default:       option_send = 0; break; //NONE
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "picture %u %u %d \"%s\"", x, y, option_send, filename );
#else
    sprintf( m_buf, "%d %u %u %d \"%s\"", Picture, x, y, option_send, filename );
#endif
    simpleCmd();
}

void EzLCD3xx::image( const char *filename, uint16_t x, uint16_t y,
                      uint32_t options )
{
    picture( filename, x, y, options );
}

void EzLCD3xx::print( int id, uint32_t align )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "print %d %s", id, jstStr(align) );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %d %s", Print, id, jstStr(align) );
#endif
    simpleCmd();
}

void EzLCD3xx::clipenable( bool enable )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "clipenable %s", enable ? "ON" : "OFF" );
#else
    sprintf( m_buf, "%d %s", ClipEnable, enable ? "ON" : "OFF" );
#endif
    simpleCmd();
}

void EzLCD3xx::cliparea( uint16_t left, uint16_t top, uint16_t right, uint16_t bottom )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "cliparea %u %u %u %u", left, top, right, bottom );
#else
    sprintf( m_buf, "%d %u %u %u %u", ClipArea, left, top, right, bottom );
#endif
    simpleCmd();
}

void EzLCD3xx::wait( unsigned long timeout )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "wait" );
#else
    sprintf( m_buf, "%d", Wait );
#endif
    sendCommand();
    if( m_verbose )
    {
        pollLCD(); // extra <CR>
        pollLCD(); // Verbose reply
    }
    if( m_echo )
        pollLCD(); // Eat <CR>    
    pollLCD( m_buf, EZLCD_BUFSIZE, timeout );
    sdebugln( "debug: wait done." );
}

void EzLCD3xx::waitt( unsigned long timeout )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "waitt" );
#else
    sprintf( m_buf, "%d", Waitt );
#endif
    sendCommand();
    if( m_verbose )
    {
        pollLCD(); // extra <CR>
        pollLCD(); // Verbose reply
    }
    if( m_echo )
        pollLCD(); // Eat <CR>    
    pollLCD( m_buf, EZLCD_BUFSIZE, timeout );
    sdebugln( "debug: waitt done." );
}

void EzLCD3xx::waitnt( unsigned long timeout )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "waitnt" );
#else
    sprintf( m_buf, "%d", Waitn );
#endif
    sendCommand();
    if( m_verbose )
    {
        pollLCD(); // extra <CR>
        pollLCD(); // Verbose reply
    }
    if( m_echo )
        pollLCD(); // Eat <CR>    
    pollLCD( m_buf, EZLCD_BUFSIZE, timeout );
    sdebugln( "debug: waitnt done." );
}

void EzLCD3xx::print( const char *str, uint32_t align )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "print %s %s", str, jstStr(align) );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %s %s", Print, str, jstStr(align) );
#endif
    simpleCmd();
}

void EzLCD3xx::record( const char *name )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "record %s", name );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %s", Rec_Macro, name );
#endif
    simpleCmd();
}

void EzLCD3xx::play( const char *name )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "play %s", name );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d %s", Play_Macro, name );
#endif
    simpleCmd();
}

void EzLCD3xx::stop()
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "stop" );
#else
    sprintf( m_buf, "%d", Stop_Macro );
#endif
    simpleCmd();
}

void EzLCD3xx::pause( int msec )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "pause %d", msec );
#else
    sprintf( m_buf, "%d %d", Pause_Macro, msec );
#endif
    simpleCmd();
}

void EzLCD3xx::loop( bool val )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "loop %u", val ? 1 : 0  );
#else
    sprintf( m_buf, "%d %u", Loop_Macro, val ? 1 : 0 );
#endif
    simpleCmd();
}

void EzLCD3xx::speed( int msec )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "speed %d", msec );
#else
    sprintf( m_buf, "%d %d", Speed_Macro, msec );
#endif
    simpleCmd();
}

void EzLCD3xx::cfgio( uint8_t port, uint8_t type )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "cfgio %u %u", port, type );
#else
    sprintf( m_buf, "%d %u %u", ConfigIO, port, type );
#endif
    simpleCmd();
}

void EzLCD3xx::io( uint8_t port, int data )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "io %u %d", port, data );
#else
    sprintf( m_buf, "%d %u %d", IO, port, data );
#endif
    simpleCmd();
}

int EzLCD3xx::io( uint8_t port )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "io %u", port );
#else
    sprintf( m_buf, "%d %u", IO, port );
#endif
    int ret = simpleCmd();
    if( ret > 0 )
    {
        ret = readInt();
        sdebug( "debug: io @ port " );
        sdebug( port );
        sdebug( " = " );
        sdebugln( ret );
        return ret;
    }
    sdebugln( "io read failed" );
    return (uint8_t)-1;
}

int EzLCD3xx::choice( const char *str, int theme, unsigned long timeout )
{
#if !EZLCD_MINIMALTX
    snprintf( m_buf, EZLCD_BUFSIZE, "choice \"%s\" %d", str, theme );
#else
    snprintf( m_buf, EZLCD_BUFSIZE, "%d \"%s\" %d", Choice, str, theme );
#endif
    simpleCmd();
    int ret;
    do
        ret = pollLCD( m_buf, EZLCD_BUFSIZE, timeout );
    while( ret == 3 && m_buf[0] == 'B' );
    ret = readInt();
    sdebug( "debug: choice = " );
    sdebugln( ret );
    return ret;
}

void EzLCD3xx::theme( int index, int EmbossDkColor, int EmbossLtColor,
                      int TextColor0, int TextColor1, int TextColorDisabled,
                      int Color0, int Color1, int colorDisabled,
                      int CommonBkColor, int fontw )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "theme %d %d %d %d %d %d %d %d %d %d %d",
        index, EmbossDkColor, EmbossLtColor,
        TextColor0, TextColor1, TextColorDisabled,
        Color0, Color1, colorDisabled, CommonBkColor, fontw );
#else
    sprintf( m_buf, "%d %d %d %d %d %d %d %d %d %d %d %d", Widget_Theme,
        index, EmbossDkColor, EmbossLtColor,
        TextColor0, TextColor1, TextColorDisabled,
        Color0, Color1, colorDisabled, CommonBkColor, fontw );
#endif
    simpleCmd();
}

void EzLCD3xx::button( int id, int theme, int strid,
                       uint16_t x, uint16_t y, uint16_t width, uint16_t height, 
                       uint16_t radius, uint32_t align, uint32_t option )
{
    int align_send, option_send;
    switch( option )
    {
        case REMOVE:   option_send = 0; break;
        case DISABLED: option_send = 2; break;
        case PRESSED:  option_send = 3; break;
        case TOGGLE:   option_send = 4; break;
        //case DRAW:     option_send = 1; break;
        default:       option_send = 1; break;
    }
    switch( align )
    {
        case RIGHT:  align_send = 1; break;
        case LEFT:   align_send = 2; break;
        case BOTTOM: align_send = 3; break;
        //case CENTER: align_send = 0; break;
        default: align_send = 0; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "button %d %u %u %u %u %u %u %u %u %d",
             id, x, y, width, height, option_send,
             align_send, radius, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %u %u %u %u %d", 
             Set_Button, id, x, y, width, height, option_send,
             align_send, radius, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::checkbox( int id, int theme, int strid,
                         uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                         uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:   option_send = 0; break;
        case DISABLED: option_send = 2; break;
        case PRESSED:  option_send = 3; break;
        case REDRAW:   option_send = 4; break;
        //case DRAW:     option_send = 1; break;
        default:       option_send = 1; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "checkbox %d %u %u %u %u %d %d %d",
             id, x, y, width, height, option_send, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d", Set_CheckBox,
             id, x, y, width, height, option_send, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD(); // More stuff coming back in verbose
}

void EzLCD3xx::radio( int id, int theme, int strid,
                      uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                      uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:           option_send = 0; break;
        //case DRAW:           option_send = 1; break;
        case DISABLED:         option_send = 2; break;
        case CHECKED:          option_send = 3; break;
        case FIRST:            option_send = 4; break;
        case FIRSTANDSELECTED: option_send = 5; break;
        default:               option_send = 1; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "radio %d %u %u %u %u %d %d %d",
             id, x, y, width, height, option_send, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d", Set_RadioButton,
             id, x, y, width, height, option_send, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::gbox( int id, int theme, int strid, 
                     uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                     uint32_t option )
{
    int option_send;
    switch( option )
    {
        case DISABLED: option_send = 2; break;
        case RIGHT:    option_send = 3; break;
        case CENTER:   option_send = 4; break;
        //case LEFT:     option_send = 1; break;
        default:       option_send = 1; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "gbox %d %u %u %u %u %d %d %d",
             id, x, y, width, height, option_send, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d", Set_Gbox,
             id, x, y, width, height, option_send, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::progress( int id, int theme,
                         uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                         int position, int range, int orientation, uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE: 
			option_send = 0;
			break;
		case DISABLED:
			if( orientation == HORIZONTAL )
				option_send = 2;
			else // VERTICAL
				option_send = 4;
			break;
        case REDRAW:
			option_send = 5;
			break;
        default: // DRAW
			if( orientation == HORIZONTAL )
				option_send = 1;
			else // VERTICAL
				option_send = 3;
			break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "progress %d %u %u %u %u %d %d %d %d",
             id, x, y, width, height, option_send, position, range, theme );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d %d", Set_Progress,
             id, x, y, width, height, option_send, position, range, theme );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::statictext( int id, int theme, int strid,
                            uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                            uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:   option_send = 0; break;
        //case LEFT:   option_send = 1; break;
        case DISABLED: option_send = 2; break;
        case RIGHT:    option_send = 3; break;
        case CENTER:   option_send = 4; break;
        case REDRAW:   option_send = 5; break;
        default:       option_send = 1; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "static %d %u %u %u %u %d %d %d",
             id, x, y, width, height, option_send, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d", Set_StaticText,
             id, x, y, width, height, option_send, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD(); // More data coming back in verbose
}

void EzLCD3xx::dmeter( int id, int theme,
                       uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                       int value, int digits, int dotpos, bool framed, uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:   option_send = 0; break;
        //case LEFT:   option_send = 1; break;
        case DISABLED: option_send = 2; break;
        case RIGHT:    option_send = 3; break;
        case CENTER:   option_send = 4; break;
        case REDRAW:   option_send = 6; break;
        default:       option_send = 1; break;
    }
    if( framed && option_send >= 1 && option_send <= 4 )
        option_send += 10;
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "dmeter %d %u %u %u %u %d %d %d %d %d",
             id, x, y, width, height, option_send, value, digits, dotpos, theme );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d %d %d", Set_DMeter,
             id, x, y, width, height, option_send, value, digits, dotpos, theme );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::ameter( int id, int theme, int strid,
                       uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                       int initial, int min, int max, uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:   option_send = 0; break;
        //case DRAW:   option_send = 1; break;
        case DISABLED: option_send = 2; break;
        case RING:     option_send = 3; break;
        case ACCURACY: option_send = 4; break;
        case REDRAW:   option_send = 5; break;
        default:       option_send = 1; break;
    }
 
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "ameter %d %u %u %u %u %d %d %d %d %d %d",
             id, x, y, width, height, option_send, initial, min, max, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d %d %d %d", Set_AMeter,
             id, x, y, width, height, option_send, initial, min, max, theme, strid );
#endif // end !EZLCD_MINIMALTX
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::dial( int id, int theme, int strid,
                     uint16_t x, uint16_t y, uint16_t radius,
                     int resolution, int initial, int max, uint32_t option )
{
    int option_send;
    switch( option )
    {
        case REMOVE:           option_send = 0; break;
        //case DRAW:           option_send = 1; break;
        case DISABLED:         option_send = 2; break;
        case CLOCKWISE:        option_send = 3; break;
        case COUNTERCLOCKWISE: option_send = 4; break;
        default:               option_send = 1; break;
    }
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "dial %d %u %u %u %d %d %d %d %d %d",
             id, x, y, radius, option_send, resolution, initial, max, theme, strid );
#else
    sprintf( m_buf, "%d %d %u %u %u %d %d %d %d %d %d", Set_Dial,
             id, x, y, radius, option_send, resolution, initial, max, theme, strid );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::slider( int id, int theme,
                 uint16_t x, uint16_t y, uint16_t width, uint16_t height,
                 int orientation, bool scrollbar,
                 int initial, int range, uint32_t option, int page )
{
    int option_send=1; // draw horizontal slider by default
    if( option == REMOVE )
        option_send = 0;
    if( option == DRAW ) // this would normally be "else if"
    {                    // but Arduino compiler can't handle it here
        if( orientation == HORIZONTAL )
        {
            if( scrollbar )
                option_send = 5;
            else // slider
                option_send = 1;
        }
        else // VERTICAL
        {
            if( scrollbar )
                option_send = 7;
            else // slider
                option_send = 3;
        }
    }
    if( option == DISABLED )
    {
        if( orientation == HORIZONTAL )
        {
            if( scrollbar )
                option_send = 6;
            else // slider
                option_send = 2;
        }
        else // VERTICAL
        {
            if( scrollbar )
                option_send = 8;
            else // slider
                option_send = 4;
        }    
    }
    if( option == REDRAW )
        option_send = 9;
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "slider %d %u %u %u %u %d %d %d %d %d",
             id, x, y, width, height, option_send, range, page, initial, theme );
#else
    sprintf( m_buf, "%d %d %u %u %u %u %d %d %d %d %d", Set_Slider, 
             id, x, y, width, height, option_send, range, page, initial, theme );
#endif
    simpleCmd();
    if( m_verbose )
        pollLCD();
}

void EzLCD3xx::ameter_value( int id, int value )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "ameter_value %d %d", id, value );
#else
    sprintf( m_buf, "%d %d %d", AMeter_Value, id, value );
#endif
    simpleCmd();
}

void EzLCD3xx::progress_value( int id, int value )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "progress_value %d %d", id, value );
#else
    sprintf( m_buf, "%d %d %d", Progress_Value, id, value );
#endif
    simpleCmd();
}

void EzLCD3xx::static_value( int id, const char *value )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "static_value %d %s", id, value );
#else
    sprintf( m_buf, "%d %d %s", StaticText_Value, id, value );
#endif
    simpleCmd();
}

void EzLCD3xx::dmeter_value( int id, int value )
{
#if !EZLCD_MINIMALTX
    sprintf( m_buf, "dmeter_value %d %d", id, value );
#else
    sprintf( m_buf, "%d %d %d", DMeter_Value, id, value );
#endif
    simpleCmd();
}

bool EzLCD3xx::wasPressed( int id )
{
    return m_buf[0]=='B' && m_buf[1]=='P'
        && atoi( m_buf+2 ) == id;
}

bool EzLCD3xx::wasReleased( int id )
{
    return m_buf[0]=='B' && m_buf[1]=='R'
        && atoi( m_buf+2 ) == id;
}

bool EzLCD3xx::wasChecked( int id )
{
    return m_buf[0]=='C' && m_buf[1]=='C'
        && atoi( m_buf+2 ) == id;
}

bool EzLCD3xx::wasUnchecked( int id )
{
    return m_buf[0]=='C' && m_buf[1]=='U'
        && atoi( m_buf+2 ) == id;
}

bool EzLCD3xx::wasSelected( int id )
{
    return m_buf[0]=='R' && m_buf[1]=='C'
        && atoi( m_buf+2 ) == id;
}

int EzLCD3xx::getSlider( int id )
{
    if( m_buf[0]!='S' || m_buf[1]!='L' )
        return -1;
    char *ptr = m_buf + 2;
    if( readInt(ptr) != id )
        return -1;
    return readInt(ptr);
}

EzLCD3xx_HW::EzLCD3xx_HW()
{
    m_pStream = &Serial;
}

void EzLCD3xx_HW::begin( long baud )
{
    Serial.begin( baud );
    findEzLCD();
#if EZLCD_MINIMALRX
    echo( false );
    verbose( false );
#endif
    sdebugln( "ezLCD initialization done" );
}


EzLCD3xx_SW::EzLCD3xx_SW( int rx, int tx )
: m_sws( rx, tx )
{
    m_pStream = &m_sws;
}

void EzLCD3xx_SW::begin( long baud )
{
#if EZLCD_SERIALDEBUG
    if( !s_sdebugInit )
    {
        // If the serial port was not intialized already,
        // do so now to enable debugging over it
        Serial.begin(9600);
        sdebugln( "\n* * * * *\nSerial debug initialized" );
        s_sdebugInit = true;
    }
#endif
    m_sws.begin( baud );
    findEzLCD();
#if EZLCD_MINIMALRX
    echo( false );
    verbose( false );
#endif
    sdebugln( "ezLCD initialization done" );
}

#if EZLCD_SERIALDEBUG
void EzLCD3xx_SW::forwardSerial()
{
    while( Serial.available() )              // There are bytes from PC to be read
    {
        char ch = Serial.read();             // Read a char
        if( ch == '\n' )                     // Convert <LF> to <CR>, when needed
            ch = '\r';
        m_pStream->write( ch );              // Write to the serial stream.
        if( ch == '\r' )                     // Read response after <CR> was
        {                                    // sent from PC
            sdebugln("User command issued");
            while( pollLCD() > 0 );
        }
    }
}
#endif
